﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Text;
using Cloak.Globalization;
using Ruleweaver.Rules;

namespace Ruleweaver.Rules.CheckMethods
{
	[ContractClass(typeof(CheckMethodContract))]
	public abstract class CheckMethod : ICheckMethod
	{
		#region ICheckMethod

		public CheckRule GetRule(Type targetType)
		{
			if(!SupportsTargetType(targetType))
			{
				throw new RuleweaverException(Resources.UnsupportedTargetType.FormatInvariant(targetType, GetType()));
			}

			targetType = GetEffectiveTargetType(targetType);

			var checkType = GetCheckType(targetType);

			return Rule.Check(GetMethod(targetType, checkType), GetCheckArguments(targetType));
		}
		#endregion

		protected abstract bool SupportsTargetType(Type targetType);

		protected virtual Type GetEffectiveTargetType(Type targetType)
		{
			Contract.Requires(targetType != null);
			Contract.Ensures(Contract.Result<Type>() != null);

			return targetType;
		}

		protected abstract MethodInfo GetMethod(Type targetType, Type checkType);

		protected virtual IEnumerable<object> GetCheckArguments(Type targetType)
		{
			Contract.Requires(targetType != null);
			Contract.Ensures(Contract.Result<IEnumerable<object>>() != null);

			return Enumerable.Empty<object>();
		}

		[Pure]
		protected static Type GetCheckType(Type targetType)
		{
			Contract.Requires(targetType != null);

			return typeof(ICheckable<>).MakeGenericType(targetType);
		}
	}

	[ContractClassFor(typeof(CheckMethod))]
	internal abstract class CheckMethodContract : CheckMethod
	{
		protected override bool SupportsTargetType(Type targetType)
		{
			Contract.Requires(targetType != null);

			return false;
		}

		protected override MethodInfo GetMethod(Type targetType, Type checkType)
		{
			Contract.Requires(targetType != null);
			Contract.Requires(checkType != null);
			Contract.Ensures(Contract.Result<MethodInfo>() != null);

			return null;
		}
	}
}